package Servicios;

import DAO.EstacionTrabajoDAO;
import DAO.ReservaEstacionDAO;
import DAO.ReservaSalaDAO;
import DAO.UsuarioDAO;
import Entidad.EstacionTrabajo;
import Entidad.ReservaEstacion;
import Entidad.ReservaSala;
import Entidad.Usuario;
import Singletons.Mensajes;
import VO.ReservaEstacionVO;
import VO.ReservaExtendidaVO;
import VO.ReservaSalaVO;
import VO.UsuarioVO;
import java.text.SimpleDateFormat;
import javax.persistence.EntityManager;
import java.util.*;
import java.util.ArrayList;
import javax.mail.Message.RecipientType;
import javax.mail.*;
import javax.mail.internet.*;

public class ServicioReservaEstacion {

    /**
     * <p>Registra uan nueva reserva en la base de datos.</p>
     * @param usuario, estacionSeleccionada, fecha, horaIni, horaFin.
     * @return GUARDADO_RESERVA_CORRECTO si se completa la reservacion, ERROR_GUARDANDO en caso contrario.
     */
    public String hacerReservacion(Usuario usuario, long estacionSeleccionada, Date fecha, double horaIni, double horaFin,EntityManager em){
        EstacionTrabajoDAO etdao = new EstacionTrabajoDAO();
        ReservaEstacion registro = new ReservaEstacion(usuario, etdao.Buscar(estacionSeleccionada,em), String.valueOf(horaIni), String.valueOf(horaFin), fecha);
        ReservaEstacionDAO re = new ReservaEstacionDAO();
        try {
            re.Crear(registro,em);
        }catch(Exception e){
            return Mensajes.ERROR_GUARDANDO;
        }
        return Mensajes.GUARDADO_RESERVA_CORRECTO;
    }
    /**
     * <p> crea una nueva reservacion y trata de guardarla en la abse de datos </p>
     * @param u
     * @param idEstacion
     * @param fecha
     * @param horaReserva
     * @param horaPrestamo
     * @param em
     * @return objeto con la informacion de la reserva guardada o null si no pudo guardarla
     */
    public ReservaEstacion hacerReservacion(Usuario u,long idEstacion,Date fecha,String horaReserva,String horaPrestamo,EntityManager em){
        EstacionTrabajoDAO etdao = new EstacionTrabajoDAO();
        ReservaEstacion registro = new ReservaEstacion(u, etdao.Buscar(idEstacion,em), horaReserva, horaPrestamo, fecha);
        ReservaEstacionDAO redao = new ReservaEstacionDAO();
        try{
            redao.Crear(registro,em);
            return registro;
        }catch(Exception e){
            return null;
        }
    }
    /**
     * <p> recibe una reserva,verifica que siga vigente, si es asi
     * setea la hora actual y devuelve la misma reserva, sino devuelve
     * la recerva que este vigente (o null si no la hay)</p>
     * @param rvo objeto con informacion de la reserva
     * @param em
     * @return
     */
    public ReservaEstacionVO confirmarReserva(ReservaEstacionVO rvo,EntityManager em) {
            ReservaEstacion re = vo_a_entidad(rvo, em);
            String horaPrestamo = horaActual(0)+"---:--";
            re.setHorarioPrestamo(horaPrestamo);
            ReservaEstacionDAO redao = new ReservaEstacionDAO();
            boolean flag = redao.actualizar(re, em);
            if(flag){
                rvo.setHorarioPrestamo(horaPrestamo);
                rvo.setEstado(Mensajes.OCUPADA);
            }
        return rvo;
    }
    /**
     * <p> determina si esxiste una reserva vigente a la hora de llamar la funcion,
     * si es asi, devuelve la eserva </p>
     * @param id de una estacion de trabajo, fecha inicio, fecha fin
     * @param em entity manager
     * @return objeto con informacion de la reserva
     */
    public ReservaEstacionVO buscarReservaActual(long id, Calendar fechaBuscada, Calendar fechaBuscadaFin, EntityManager em) {
        Calendar fechaActual ;
        Calendar fechaActualFin ;
        if (fechaBuscada == null){
              fechaActual = Calendar.getInstance();
              fechaActualFin = Calendar.getInstance();
        }else{
            fechaActual = fechaBuscada;
            fechaActualFin = fechaBuscadaFin;
        }
        ReservaEstacionDAO dao = new ReservaEstacionDAO();
        ArrayList<ReservaEstacion> lista2 = null;
        try{
            lista2 = dao.lista(em);
        }catch(Exception e){
            return null;
        }
        ReservaEstacionVO revo =  null;
        ArrayList<ReservaEstacion> lista = new ArrayList<ReservaEstacion>();
        for(ReservaEstacion u : lista2){
            if(u.getEstacion().getId()==id) lista.add(u);
        }
        for(ReservaEstacion u : lista){
            if(u.getHorarioPrestamo()==null){
            }
            else if(u.getHorarioPrestamo().equals(Mensajes.FALLA_RESERVA)) continue;
            Calendar f1 = Calendar.getInstance();
            f1.setTime(u.getFecha());
            if(f1.get(Calendar.MONTH)==fechaActual.get(Calendar.MONTH) && f1.get(Calendar.DATE)==fechaActual.get(Calendar.DATE)){
                String horaReserva = u.getHorarioReserva();
                if(horaReserva==null){
                    String horarioPrestamo = u.getHorarioPrestamo();
                    if(horarioPrestamo.substring(6, 11).equals("--:--")){
                        revo = entidad_a_vo(u, Mensajes.OCUPADA);
                        //horaReserva.substring(1);
                        break;
                    }
                }
                else{
                    int horaInicio = Integer.parseInt(horaReserva.substring(0,2));
                    int minInicio = Integer.parseInt(horaReserva.substring(3,5));
                    int horaFin = Integer.parseInt(horaReserva.substring(6,8));
                    int minFin = Integer.parseInt(horaReserva.substring(9,11));
                    Calendar fechaInicio = Calendar.getInstance();
                    fechaInicio.set(Calendar.HOUR_OF_DAY , horaInicio);
                    fechaInicio.set(Calendar.MINUTE , minInicio);
                    Calendar fechaFin = Calendar.getInstance();
                    fechaFin.set(Calendar.HOUR_OF_DAY , horaFin);
                    fechaFin.set(Calendar.MINUTE , minFin);
                    if(fechaActual.after(fechaInicio) && fechaActualFin.before(fechaFin)){
                        String estadoReserva = u.getHorarioPrestamo();
                        if(estadoReserva==null){
                            revo = entidad_a_vo(u, Mensajes.RESERVADA);
                            break;
                        }
                        else{
                            if(estadoReserva.substring(6, 11).equals("--:--")){
                                revo = entidad_a_vo(u, Mensajes.OCUPADA);
                                break;
                            }
                        }
                    }
                }
                
            }
        }
        return revo;
    }

    public ReservaEstacionVO entidad_a_vo(ReservaEstacion u, int estado){
        ReservaEstacionVO revo = new ReservaEstacionVO();
        revo.setId(u.getId());
        revo.setEstado(estado);
        revo.setHorarioPrestamo(u.getHorarioPrestamo());
        revo.setHorarioReserva(u.getHorarioReserva());
        revo.setUsuario(u.getUsuario().getNombre());
        revo.setEstacion(u.getId());
        return revo;
    }

    public boolean finalizarReserva(ReservaEstacionVO revo, EntityManager em) {
        ReservaEstacion re = vo_a_entidad(revo, em);
        String horario = re.getHorarioPrestamo();
        re.setHorarioPrestamo(horario.substring(0,6)+horaActual(0));
        ReservaEstacionDAO redao = new ReservaEstacionDAO();
        return redao.actualizar(re, em);
    }
    /**
     * <p>Busca y retorna las reservas de la estacion cuyo id sea idEstacion y la fecha sea la indicada en Date</p>
     * @param idEstacion Long con el id de la Estacion cuyas reservas se requieren
     * @param fecha Date con la fecha de las reservas que se necesitan
     * @param em EntityManager que se conecta con la base de datos
     * @return lista de ReservaEstacionVO de esa sala en esa fecha
     */
    public ArrayList<ReservaEstacionVO> getReservasEstacion(long idEstacion,Date fecha,EntityManager em){
        ArrayList<ReservaEstacion> reservasEstacion = new ReservaEstacionDAO().listaReservasEstacion(idEstacion,em);
        ArrayList<ReservaEstacionVO> reservasEstacionFecha = new ArrayList<ReservaEstacionVO>();
        SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");
        String stringFecha=dateFormat.format(fecha);
        for(ReservaEstacion reservaEstacion:reservasEstacion)
            if(dateFormat.format(reservaEstacion.getFecha()).equals(stringFecha))
                reservasEstacionFecha.add(reservaEstacion_a_VO(reservaEstacion));
        return reservasEstacionFecha;
    }
    /**
     * <p> busca una entidad en la base de datos que tenga las mismas caracteristicas
     * que el objeto que se le pasa como parametro </p>
     * @param revo vo de una reserva
     * @param em entity Manager
     * @return objeto con toda la informacion de una reserva
     */
    public ReservaEstacion vo_a_entidad(ReservaEstacionVO revo, EntityManager em){
        ReservaEstacionDAO redao = new ReservaEstacionDAO();
        return redao.Buscar(revo.getId(), em);
    }
    /**
     * <p>Toma una ReservaEstacion y devuelve su version VO.</p>
     * @param reservaEstacion ReservaEstacion
     * @return reservaEstacionVO con la misma informacion del argumento.
     */
    private ReservaEstacionVO reservaEstacion_a_VO(ReservaEstacion reservaEstacion){
        ReservaEstacionVO reservaEstacionVO = new ReservaEstacionVO();
        reservaEstacionVO.setUsuario(reservaEstacion.getUsuario().getCorreo());
        reservaEstacionVO.setEstacion(reservaEstacion.getEstacion().getId());
        reservaEstacionVO.setHorarioPrestamo(reservaEstacion.getHorarioPrestamo());
        reservaEstacionVO.setHorarioReserva(reservaEstacion.getHorarioReserva());
        return reservaEstacionVO;
    }
   /**
     * <p> devuelve la hora actual en el formato hh:mm </p>
     * @return String
     */
    public static String horaActual(int numHorasDesfase) {
        Calendar fecha = Calendar.getInstance();
        int hora = fecha.get(Calendar.HOUR_OF_DAY);
        if (hora + numHorasDesfase < 24)
            hora += numHorasDesfase;
        int min = fecha.get(Calendar.MINUTE);
        String r = "";
        if(hora<10){
            r = "0"+String.valueOf(hora);
        }
        else{
            r = String.valueOf(hora);
        }
        if(min<10){
            r = r+":"+"0"+String.valueOf(min);
        }
        else{
            r = r+":"+String.valueOf(min);
        }
        return r;
    }
    /**
     * <p>Verifica si el entero pasado como argumento corresponde al dia domingo</p>
     * @param dia Integer con el numero del dia
     * @return true si dia es igual a 7
     */
    public boolean diaDomingo(int dia){
        return dia==1;
    }
    /**
     * <p>Verifica que el tiempo de reserva sea menor que el maximo definido.</p>
     * @param horario String con un horario en el formato hh:mm-hh:mm
     * @return false si el tiempo maximo se supera, true si no
     */
    public boolean tiempoMaximoReserva(String horario){
        Hora hInicio=new Hora(horario.substring(0,5));
        Hora hFin=new Hora(horario.substring(6));
        int minutosIni=hInicio.getHoras()*60+hInicio.getMinutos();
        int minutosFin=hFin.getHoras()*60+hFin.getMinutos();
        int diferenciaMaxima=Mensajes.TIEMPO_MAXIMO_RESERVA_EQUIPO*60;
        return minutosFin-minutosIni<=diferenciaMaxima;
    }

    public ReservaEstacionVO reservarEstacion(UsuarioVO usuario, long id, String horario, Date fecha, EntityManager em) {
        UsuarioDAO udao = new UsuarioDAO();
        EstacionTrabajoDAO etdao = new EstacionTrabajoDAO();
        ReservaEstacionDAO redao = new ReservaEstacionDAO();
        Usuario u = udao.encontrarUsuarioPorCorreo(usuario.getCorreo(), em);
        if(u==null) return null;
        EstacionTrabajo et = etdao.Buscar(id, em);
        if(et==null) return null;
        ReservaEstacion re = new ReservaEstacion(u, et, horario, null, fecha);
        redao.Crear(re,em);
        ReservaEstacionVO revo = new ReservaEstacionVO();
        revo.setEstado(Mensajes.RESERVADA);
        revo.setHorarioPrestamo(re.getHorarioPrestamo());
        revo.setHorarioReserva(re.getHorarioReserva());
        revo.setUsuario(re.getUsuario().getNombre());
        revo.setEstacion(re.getEstacion().getNumero());
        return revo;
    }

    private String convertir(Calendar fecha, Calendar fechaFin) {

        int horaI = fecha.get(Calendar.HOUR_OF_DAY);
        int minI = fecha.get(Calendar.MINUTE);
        int horaF = fechaFin.get(Calendar.HOUR_OF_DAY);
        int minF = fechaFin.get(Calendar.MINUTE);

        String horario;

        if(horaI>9)
            horario = String.valueOf(horaI)+":";
        else
            horario = "0"+String.valueOf(horaI)+":";
        if(minI>9)
            horario += String.valueOf(minI)+"-";
        else
            horario += "0"+String.valueOf(minI)+"-";
        if(horaF>9)
            horario += String.valueOf(horaF)+":";
        else
            horario += "0"+String.valueOf(horaF)+":";
        if(minF>9)
            horario += String.valueOf(minF);
        else
            horario += "0"+String.valueOf(minF);



        return horario;
    }

    public ReservaSalaVO[] listaReservasSala(EntityManager em, Long idSala) {
        ReservaSalaDAO rsdao = new ReservaSalaDAO();
        ArrayList<ReservaSala> listaReservasSala = rsdao.listaReservasSala(idSala, em);
        ArrayList<ReservaSalaVO> listaReservaSalaVOs = new ArrayList<ReservaSalaVO>();
        for (ReservaSala rs:listaReservasSala){
            if(rs.getHorarioPrestamo()==null){
            listaReservaSalaVOs.add(reservaSala_a_reservaSalaVO(rs));
            }
        }
        ReservaSalaVO [] arregloSalaVO =new ReservaSalaVO[listaReservaSalaVOs.size()];
        listaReservaSalaVOs.toArray(arregloSalaVO);

        return arregloSalaVO;
    }

    private ReservaSalaVO reservaSala_a_reservaSalaVO(ReservaSala reservaSala) {
        ReservaSalaVO rsvo = new ReservaSalaVO();
        rsvo.setAdministrador(reservaSala.getAdministrador().getNombre());
        rsvo.setEstado(0);
        rsvo.setHorarioPrestamo(reservaSala.getHorarioPrestamo());
        rsvo.setHorarioReserva(reservaSala.getHorarioReserva());
        rsvo.setId(reservaSala.getId());
        rsvo.setProfesor(reservaSala.getProfesor());
        rsvo.setSala(reservaSala.getSala().getNombre());

        return rsvo;
    }
    /**
     * <p>Clase Hora para el manejo de horas</p>
     */
    class Hora{
        int horas;
        int minutos;
        public Hora(String hora){
            try{
                horas=Integer.parseInt(hora.substring(0,2));
                minutos=Integer.parseInt(hora.substring(3));
            }catch(NumberFormatException n){
                horas=minutos=0;
            }
        }
        public int getHoras(){
            return horas;
        }
        public int getMinutos(){
            return minutos;
        }
        public boolean esMenor(Hora hora){
            if(horas>hora.horas)
                return false;
            if(horas<hora.horas)
                return true;
            if(minutos>hora.minutos)
                return false;
            if(minutos<hora.minutos)
                return true;
            return false;
        }
        public boolean esMayor(Hora hora){
            if(esIgual(hora))
                return false;
            return !esMenor(hora);
        }
        public boolean esIgual(Hora hora){
            return horas==hora.horas && minutos==hora.minutos;
        }
    }
    /**
     * <p>Verifica horario se solapa con alguno de los horarios de las reservas en la lista reservasVO</p>
     * @param horario String con un horario en formato hh:mm-hh:mm
     * @param reservasVO ArrayList<ReservaSalaVO>
     * @return false si se solapan los horarios, true si no
     */
    public boolean estaDisponible(String horario,ArrayList<ReservaEstacionVO> reservasVO){
        for(ReservaEstacionVO reservaVO:reservasVO)
            if(!horariosCompatibles(horario,reservaVO.getHorarioReserva()))
                return false;
        return true;
    }
    /**
     * <p>Compara las dos horas a ver si se solapan</p>
     * @param horario1 String con un valor de horario en el formato hh:mm-hh:mm
     * @param horario2 String con un valor de horario en el formato hh:mm-hh:mm
     * @return true si no se solapan las horas, false si si
     */
    public boolean horariosCompatibles(String horario1,String horario2){
        Hora h1Inicio=new Hora(horario1.substring(0,5));
        Hora h1Fin=new Hora(horario1.substring(6));
        Hora h2Inicio=new Hora(horario2.substring(0,5));
        Hora h2Fin=new Hora(horario2.substring(6));
        if(h2Inicio.esMenor(h1Fin) && h2Fin.esMayor(h1Inicio))
            return false;
        return true;
    }
    /**
     * <p>Verifica si la fecha pasada como argumento tiene una diferencia minima
     * de DIAS_MINIMOS_RESERVA_ESTACION con la fecha actual.</p>
     * @param fechaReserva Date con la fecha de una reserva
     * @return true si cumple con la diferencia minima, false si no
     */
    public boolean diasMinimosReserva(Date fechaReserva){
        Date fechaHoy=new Date();
        Date fechaMinima=new Date(fechaHoy.getYear(),fechaHoy.getMonth(),fechaHoy.getDate()+Mensajes.DIAS_MINIMOS_RESERVA_ESTACION-1);
        return fechaReserva.after(fechaMinima);
    }

    /**
     * <p>Envia un correo electronico a todos los usuarios quetengan una reservacion al otro dia</p>
     * @param informacion ArrayList<String[]> contiene el correo, la hora y el lugar de la reserva de cada usuario al que se le va a enviar el correo electronico
     * @throws MessagingException si ArrayList<String[]> informacion no tiene el formato adecuado o si ocurre algun problema con el envio de los correos
     */
    public void enviarCorreo(ArrayList<String[]> informacion) throws MessagingException {
        if(informacion==null)
            throw new MessagingException("Lista de correos nula.");
        if(informacion.isEmpty())
            return;
        String asunto="Recordatorio de tu reservación";
        String correo="softstation07@gmail.com";
        String contrasena="12345asdfg";
        Properties prop=new Properties();
        prop.setProperty("mail.smtp.host","smtp.gmail.com");
        prop.setProperty("mail.smtp.starttls.enable","true");
        prop.setProperty("mail.smtp.port","587");
        prop.setProperty("mail.smtp.user",correo);
        prop.setProperty("mail.smtp.auth","true");
        Session session=Session.getDefaultInstance(prop);
        MimeMessage mensaje;
        Transport transport=null;
        String texto;
        String cabecera="<h1 style=\"font-family:Segoe UI,Tahoma;color:#3B75C2;margin-bottom:1em;\">Recordatorio de Reservación</h1>";
        String pie="<p style=\"font-size:90%;color:#333;margin-top:1em;\"><cite>Equipo de Reservas de la Universidad Nacional de Colombia</cite></p>";
        for(String[] infoUsuario:informacion){
            if(infoUsuario==null || infoUsuario.length!=3)
                continue;
            boolean informacionCorrupta=false;
            for(int i=0;i<infoUsuario.length;i++)
                if(infoUsuario[i]==null)
                    informacionCorrupta=true;
            if(informacionCorrupta)
                continue;
            texto="<p>Mañana tienes una reserva a las "+infoUsuario[1]+" en el(la) "+infoUsuario[2]+".</p>";
            mensaje=new MimeMessage(session);
            mensaje.setFrom(new InternetAddress(correo));
            mensaje.addRecipient(RecipientType.TO,new InternetAddress(infoUsuario[0]));
            mensaje.setSubject(asunto);
            mensaje.setText(cabecera+texto+pie,"ISO-8859-1","html");
            transport=session.getTransport("smtp");
            transport.connect(correo,contrasena);
            transport.sendMessage(mensaje,mensaje.getAllRecipients());
        }
        transport.close();
    }

    /**
     * <p>Busca y retorna informacion acerca de las reservas de estacion para el otro dia</p>
     * @param em EntityManager que se conecta con la base de datos
     * @return una lista de String[] con tres cadenas: un correo, una hora y el nombre de la sala de la reserva
     */
    public ArrayList<String[]> getInformacionReservas(EntityManager em){
        ReservaEstacionDAO dao=new ReservaEstacionDAO();
        ArrayList<ReservaEstacion> reservas=dao.lista(em);
        ArrayList<String[]> infoReservas=new ArrayList<String[]>();
        Date fechaHoy=new Date();
        Date fechaManana=new Date(fechaHoy.getYear(),fechaHoy.getMonth(),fechaHoy.getDate()+1);
        SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");
        String stringFechaManana=dateFormat.format(fechaManana);
        String[] info;
        for(ReservaEstacion reserva:reservas)
            if(dateFormat.format(reserva.getFecha()).equals(stringFechaManana)){
                if(reserva.getHorarioReserva().length()<5)
                    continue;
                info=new String[3];
                info[0]=reserva.getUsuario().getCorreo();
                info[1]=reserva.getHorarioReserva().substring(0,5);
                info[2]=reserva.getEstacion().getSala().getNombre();
                infoReservas.add(info);
            }
        return infoReservas;
    }
    public ReservaExtendidaVO[] listaReservasUsuario(EntityManager em,UsuarioVO usuario) {
        ReservaEstacionDAO redao = new ReservaEstacionDAO();

        ArrayList<ReservaEstacion> listaReservasUsuario = redao.listaReservasEstacionPorUsuario(usuario.getCedula(), em);
        ArrayList<ReservaExtendidaVO> listaReservaExtendida = new ArrayList<ReservaExtendidaVO>();
        for (ReservaEstacion re:listaReservasUsuario){
            if(re.getHorarioPrestamo()==null){
            listaReservaExtendida.add(reservaEstacion_ReservaExtendidaVO(re));
            }
        }
        ReservaExtendidaVO [] arregloReservaExtendidaVO =new ReservaExtendidaVO[listaReservaExtendida.size()] ;
        listaReservaExtendida.toArray(arregloReservaExtendidaVO);

        return arregloReservaExtendidaVO;
    }
    private ReservaExtendidaVO reservaEstacion_ReservaExtendidaVO(ReservaEstacion re){
        ReservaExtendidaVO revo = new ReservaExtendidaVO();
        revo= new ReservaExtendidaVO(re.getId(), re.getEstacion().getId(), re.getEstacion().getSala().getNombre(),
                re.getEstacion().getSala().getEdificio().getNombre(), re.getHorarioPrestamo(), re.getHorarioReserva(),
                re.getUsuario().getNombre(), re.getUsuario().getCorreo());
        return revo;
    }

    public ReservaEstacionVO buscar(long id, EntityManager em) {
        ReservaEstacionDAO redao = new ReservaEstacionDAO();
        ReservaEstacion re = redao.Buscar(id, em);
        ReservaEstacionVO revo;
        if (re!=null)
            revo = new ReservaEstacionVO(re.getId(), re.getUsuario().getNombre(), re.getEstacion().getId(), re.getHorarioPrestamo(), re.getHorarioReserva(), 0);
        else
            revo = null;
        return revo;
    }

    public void cancelarReservaUsuario(long id, EntityManager em) {
        ReservaEstacionDAO redao = new ReservaEstacionDAO();
        redao.eliminar (id, em);
    }

}